home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / 2DLab / TwoDView.m < prev    next >
Encoding:
Text File  |  1992-01-14  |  31.3 KB  |  1,235 lines

  1. /* Generated by Interface Builder */
  2.  
  3. #include <stdio.h>
  4. #include <stdlib.h> /* qsort blah blah */
  5.  
  6. #import "TwoDView.h"
  7. #import "TwoDTSP.h"
  8. #import <math.h>
  9. #import <appkit/appkit.h>
  10. #import <appkit/Matrix.h>
  11. #import <appkit/Form.h>
  12. #import <appkit/nextstd.h>
  13. #import <dpsclient/wraps.h>
  14.  
  15. #define NI PSnewinstance()
  16.  
  17. #ifdef DEBUG /* put this in so the console sould not get flooded. */
  18. // #define PRINTLOTS
  19. #endif
  20. #define MESSAGE(x) printf("%s\n",x)
  21. @implementation TwoDView
  22.  
  23. /* Map the square [-1 .. 1]x[-1 .. 1] to the TwoDView, leaving a margin
  24.  * of 10%, and centering the origin.
  25.  *
  26.  * the View must already be lockFocused.
  27.  */
  28. void setUpScaling(TwoDView *self)
  29. {
  30.   NXRect r;
  31.   [self getBounds:&r];
  32.   PStranslate(r.size.width/2,r.size.height/2);
  33.   PSscale(0.9*0.5*r.size.width,0.9*0.5*r.size.height);
  34. }
  35.  
  36. /*
  37.  * set the drawing attributes (except for instance mode flag).
  38.  * 
  39.  * The code for setting line width averages the two sides of the
  40.  * bounds rectangle, to avoid really nasty-looking lines when the view's
  41.  * aspect ratio is not close to unity.  There are probably better ways
  42.  * to do this.
  43.  *
  44.  * the View must already be lockFocused.
  45.  */
  46. void setUpDrawing(TwoDView *self)
  47. {
  48.   NXRect r;
  49.   [self getBounds:&r];
  50.   self->width=([self->lineWidthSlider floatValue]*2.0)/
  51.               (r.size.width+r.size.height);
  52.   if (self->width)
  53.     PSsetlinewidth(self->width);
  54.   else {
  55.     if (NXDrawingStatus == NX_PRINTING) PSsetlinewidth(0.001);
  56.     else PSsetlinewidth(0.0);
  57.     };
  58. }
  59.  
  60. /*
  61.  * initialize instance variables relating to the data structures.
  62.  * This is broken out of
  63.  * the newFrame: method because mouseDown: events and open: will
  64.  * also need to initialize those quantities.
  65.  *
  66.  * the instance variable numPoints contains the old value that
  67.  * we will be replacing.
  68.  *
  69.  * cases: numPoints=npoints : no alloc, but clear flags.
  70.  *        numPoints=0, npoints > 0 : alloc arrays, etc.
  71.  *        numPoints>0, npoints = 0 : clobber arrays, etc.
  72.  *        numPoints>0, npoints > 0 : realloc arrays, etc.
  73.  *     
  74.  * this function does NOT put any data in the array.  The caller must
  75.  * do that.
  76.  *
  77.  * storage is not allocated for the Delaunay, Gabriel and relative neighborhood
  78.  * graphs because they could be completely connected (large!) but usually
  79.  * aren't.  They will be allocated in the appropriate doFooGraph routines
  80.  * (below).
  81.  */
  82. void initGeometryStuff(TwoDView *self,int npoints)
  83. {
  84. #ifdef DEBUG
  85.     MESSAGE("initGeometryStuff");
  86. #endif
  87.  
  88.   if (self->numPoints == npoints) {
  89.     self->mstExists=self->chExists=self->vorExists=
  90.                     self->ggExists=self->rngExists=self->dtExists=NO;
  91.     return;
  92.     };
  93.   if (!self->numPoints) { /* alloc */
  94.     NX_MALLOC(self->data,float,npoints*2);
  95.     NX_MALLOC(self->mst_edge,int,(npoints-1)*2);
  96.     NX_MALLOC(self->ch_vertex,int,npoints);
  97.     NX_MALLOC(self->tsp_edges,int,npoints*2);
  98.     }
  99.   else {
  100.     if (!npoints) { /* clobber */
  101.       NX_FREE(self->data);
  102.       NX_FREE(self->mst_edge);
  103.       NX_FREE(self->ch_vertex);
  104.       NX_FREE(self->tsp_edges);
  105.       }
  106.     else {
  107.       NX_REALLOC(self->data,float,npoints*2);
  108.       NX_REALLOC(self->mst_edge,int,(npoints-1)*2);
  109.       NX_REALLOC(self->tsp_edges,int,npoints*2);
  110.       };
  111.     };
  112.   self->numPoints=npoints;
  113.   [self->numPointsForm setIntValue:self->numPoints at:0];
  114.   self->mstExists=self->chExists=self->vorExists=
  115.                   self->ggExists=self->rngExists=self->dtExists=NO;
  116. }
  117.  
  118. /*
  119.  * make some text appear in the `Status' box
  120.  */
  121. #define STATUS(s) [statusText setStringValue:s]
  122.  
  123. /*
  124.  * set things up.
  125.  */
  126. + newFrame:(NXRect *)frameRect
  127. {
  128.   self = [[super newFrame:frameRect] allocateGState]; /* lots of focus pocus */
  129.   numPoints=0; /* this will be fixed */
  130.   operation=OP_PRIM_MST; /* agree with .nib */
  131.   autoUpdate=1; /* agree with .nib */
  132.   srandom(time(0));
  133.   [self random:0];
  134.   displayFlag = DISP_PTS_RESULTS;
  135.   return self;
  136. }
  137.  
  138. /* create a list of 2D points to demonstrate the algms on.
  139.  * if sender is nil (zero), random: was called from initFrame. Otherwise,
  140.  * the Generate button was pressed.
  141.  */
  142.  
  143. - random:sender
  144. {
  145.   int i,n;
  146.  
  147.   if (sender && numPointsForm)
  148.     n = [numPointsForm intValueAt:0];
  149.   else
  150.     n = 10; /* must be initializing */
  151.  
  152.   initGeometryStuff(self,n);  /* numPOints gets set */
  153.  
  154.   for(i=0;i<numPoints;i++) {
  155.     X(i)=2.0*(random()&65535)/65536.0 - 1.0;
  156.     Y(i)=2.0*(random()&65535)/65536.0 - 1.0;
  157.     };
  158.  
  159.   if (sender) {
  160.     if (autoUpdate) [self animThenDisp:0];
  161.     else [self disp:0];
  162.     };
  163.   return self;
  164. }
  165.  
  166. - mouseDown:(NXEvent *)theEvent
  167. {
  168.   NXPoint where = theEvent->location;
  169.   NXRect r;
  170.   float x,y,cx,cy;
  171.   [self convertPoint:&where fromView:nil];
  172.   [self getBounds:&r];
  173.   cx=r.origin.x+0.5*r.size.width;
  174.   cy=r.origin.y+0.5*r.size.height;
  175.  
  176.   x=(where.x - cx)/(0.5*0.9*r.size.width)+0.001;
  177.   y=(where.y - cy)/(0.5*0.9*r.size.height)+0.001;
  178.   if ((fabs(x) > 1.0)||(fabs(y) > 1.0)) return self;
  179. #ifdef DEBUG
  180.   fprintf(stderr,"x %f y %f\n",x,y);
  181. #endif
  182.  
  183.   initGeometryStuff(self,numPoints+1); /* numPoints gets reset */
  184.   X(numPoints-1)=x;
  185.   Y(numPoints-1)=y;
  186.  
  187.   if (autoUpdate) [self animThenDisp:0];
  188.   else [self disp:0];
  189.   return self;
  190. }
  191.  
  192. - free
  193. { initGeometryStuff(self,0); [super free]; return self; }
  194. /*--------------------------------------------------------------------
  195. Enable the form cell if it han an optimal value.
  196. Assume that the cell is disabled.
  197.                                                  Bill Roth/1991-Nov-26
  198. --------------------------------------------------------------------*/
  199.  
  200. - setOperation:(Matrix *)sender
  201.     operation=[sender selectedRow]; 
  202.     if (operation >= OP_TSP_STUPID_NEIGHBOR) {
  203.     [optimalValueFormCell setEnabled: YES];
  204.     [timeSlider setEnabled: YES];
  205.     [timeField setEnabled: YES];
  206.     }
  207.     else {
  208.     [optimalValueFormCell setEnabled: NO];
  209.     [timeSlider setEnabled: NO];
  210.     [timeField setEnabled: NO];
  211.     [optimizeButton setEnabled: NO];
  212.     }
  213.     return self; 
  214. }
  215.  
  216. - setAutoUpdate:(Button *)sender
  217. { autoUpdate = [sender state]; return self; }
  218.  
  219.  
  220. /* This method is invoked when the user bangs on the Animate/Display button. */
  221. /*--------------------------------------------------------------------
  222. Sets the display-pts-only flad, the calls [display] to draw points.
  223. Then does the appropriate drawing routine, then when finished, calls 
  224. [disp] which will call display and tell it do display everything.
  225.  
  226.                                                  Bill Roth/1991-Nov-17
  227. --------------------------------------------------------------------*/
  228.  
  229. - animThenDisp:sender
  230. {
  231. #ifdef DEBUG
  232.     MESSAGE("animthenDisp");
  233. #endif
  234.  
  235.   /* erase prior drawing and display points only */
  236.   displayFlag = DISP_PTS_ONLY;
  237.   [self display];
  238.  
  239.   /* get ready to draw (in instance mode) */
  240.   [self lockFocus];
  241.   setUpScaling(self);
  242.   NXSetColor([self->highColorWell color]);
  243.   setUpDrawing(self);
  244.   PSsetinstance(YES);
  245.  
  246.   /* do the operation (drawing in the doFoo methods is all instance drawing) */
  247.   switch(operation) {
  248.     case OP_PRIM_MST: [self doPrimMST]; break;
  249.     case OP_KRUSKAL_MST: [self doKruskalMST]; break;
  250.     case OP_JARVIS_HULL: [self doJarvisHull]; break;
  251.     case OP_GRAHAM_HULL: [self doGrahamHull]; break;
  252.     case OP_VORONOI_DIAGRAM: [self doVoronoiDiagram]; break;
  253.     case OP_DELAUNAY_TRIANGULATION: [self doDelaunayTriangulation]; break;
  254.     case OP_GABRIEL_GRAPH: [self doGabrielGraph]; break;
  255.     case OP_RELATIVE_NEIGHBORHOOD_GRAPH: [self doRelativeNeighborhoodGraph];
  256.                                          break;
  257.     case OP_TSP_STUPID_NEIGHBOR:
  258.             [self doSimpleNearNeighbor]; break;
  259.     case OP_TSP_CHEAPEST_INSERTION: [self doCheapestInsertion]; break;
  260.     case OP_TSP_NEAREST_NEIGHBOR: [self doNearestNeighbor]; break;
  261.     case OP_TSP_NEAREST_ADDITION: [self doNearestAddition]; break;
  262.     case OP_TSP_FARTHEST_INSERTION: [self doFarthestInsertion]; break;
  263.     }
  264.  
  265.   /* clear instance mode drawing  and reset things*/
  266.   PSsetinstance(NO);
  267.   [self unlockFocus];
  268.  
  269.   /* display results */
  270.   displayFlag = DISP_PTS_RESULTS;
  271.   [self disp:0];
  272.   return self;
  273. }
  274.  
  275. -disp:sender
  276. {
  277.   [self display];
  278.   return self;
  279. }
  280.  
  281.  
  282. /* Kruskal's MST algorithm (non-optimal quadratic version) */
  283.  
  284. static float *length;
  285.  
  286. int ycompar(p1,p2)
  287.   int *p1,*p2;
  288.   {
  289.     float l1=length[*p1],l2=length[*p2];
  290.     if (l1<l2) return -1;
  291.     if (l1>l2) return 1;
  292.     return 0;
  293.   }
  294.  
  295. float cost(float *p1,float *p2) /* squd Eucl distance between 2 2D points */
  296. {
  297.   float x1,x2,y1,y2;
  298.   x1= p1[0]; y1=p1[1];
  299.   x2= p2[0]; y2=p2[1];
  300.   return((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
  301. }
  302.  
  303. /*
  304.   XXX
  305.        THIS IMPLEMENTATION IS NOT OPTIMAL
  306.   XXX
  307. */
  308.  
  309. - doKruskalMST
  310. {
  311.   int *group,groupindex=1;
  312.   int *edge,*edgptr,*index,idx;
  313.   int i;
  314.   float *lptr;
  315. #ifdef DEBUG
  316.     MESSAGE("doKruskalMST");
  317. #endif
  318.  
  319.   STATUS("sorting edges by length");
  320.   NX_MALLOC(edge,int,numPoints*(numPoints-1));
  321.   edgptr=edge;
  322.   NX_MALLOC(length,float,numPoints*(numPoints-1)/2);
  323.   lptr=length;
  324.   /* fill in edge endpoint and length arrays */
  325.   for(i=0;i<numPoints-1;i++) {
  326.     int j;
  327.     char buf[80];
  328.     sprintf(buf,"point %d",i);
  329.     STATUS(buf);
  330.     for(j=i+1;j<numPoints;j++) {
  331.       *edgptr++ = i;
  332.       *edgptr++ = j;
  333.       *lptr++ = cost(XY(i),XY(j));
  334.       };
  335.     };
  336.   STATUS("qsorting");
  337.   NX_MALLOC(index,int,numPoints*(numPoints-1)/2);
  338.   for(i=0;i<numPoints*(numPoints-1)/2;i++) index[i]=i;
  339.   /* sort edges */
  340.   qsort(index,numPoints*(numPoints-1)/2,sizeof(int),ycompar);
  341.  
  342.   NX_MALLOC(group,int,numPoints);
  343.   bzero(group,numPoints*sizeof(int));
  344.   
  345.   /* Algorithm:
  346.      FOR i = 1 to n-1 
  347.        Add (to the tree) the shortest edge that does not form a circuit in
  348.        the tree.
  349.      ROF
  350.   */
  351.   idx=0;
  352.   for(i=0;i<numPoints-1;i++) {
  353.     char flag=FALSE;
  354.     char buf[80];
  355.     sprintf(buf,"adding edge %d",i);
  356.     STATUS(buf);
  357.     while (!flag) { /* look for an edge */
  358.       int j;
  359.       int v1=edge[index[idx]*2],g1=group[v1];
  360.       int v2=edge[index[idx++]*2+1],g2=group[v2];
  361.       NI;
  362.       [self drawEdge:X(v1):Y(v1):X(v2):Y(v2)];
  363.       NXPing();
  364.       if ((!g1&&!g2)||(g1!=g2)) {
  365.         flag=TRUE;
  366.         mst_edge[i*2]=v1;
  367.         mst_edge[i*2+1]=v2;
  368.         if (g1&&!g2) group[v2]=g1;
  369.         else if(g2&&!g1) group[v1]=g2;
  370.         else if (!g1 && !g2) group[v1]=group[v2]=groupindex++;
  371.         else  /* relabel all g2's to g1 */
  372.           for(j=0;j<numPoints;j++) if (group[j]==g2) group[j]=g1;
  373.         };
  374.       };
  375.     };
  376.   NX_FREE(group);
  377.   NX_FREE(index);
  378.   NX_FREE(length);
  379.   NX_FREE(edge);
  380.   mstExists=YES;
  381.   STATUS("idle");
  382.   return self;
  383. }
  384.  
  385.  
  386. #define SIGNEDAREA(x1,y1,x2,y2,x3,y3) (x1*y2+x3*y1+x2*y3-x3*y2-x1*y3-x2*y1)
  387.  
  388. - doJarvisHull
  389. {
  390. /* Jarvis' march for planar convex hull - From Preparata and Shamos */
  391.   float ymax= -3.0e30,ymin=3.0e30,y,best_x,best_y,candidate_x,candidate_y;
  392.   float current_x,current_y,a;
  393.   int i;
  394.   int nh;
  395.   int current_index=0,best_index=0;
  396.   int min_index=0,max_index=0;
  397.   char *used;
  398.   /* 0. set up storage for indices */
  399. #ifdef DEBUG
  400.     MESSAGE("doJarvisHull");
  401. #endif
  402.  
  403.   NX_MALLOC(used,char,numPoints);
  404.   /* 1. find min & max in y-coordinate */
  405.   STATUS("finding Ymin vertex");
  406.   for(i=0;i<numPoints;i++) {
  407.     y= Y(i);
  408.     if (y<ymin) { ymin=y; min_index=i; };
  409.     if (y>ymax) { ymax=y; max_index=i; };
  410.     };
  411.   nh = 1;
  412.  
  413.   ch_vertex[0]=min_index; /* first hull vertex */
  414.  
  415.   current_index=min_index;
  416.   for(i=0;i<numPoints;i++) used[i]=FALSE;
  417.   /* 2. Jarvis' march - min to max */ 
  418.   STATUS("Jarvis' march: min to max");
  419.   while (current_index!=max_index) {
  420.     used[current_index]=TRUE;
  421.     current_x= X(current_index);
  422.     current_y= Y(current_index);
  423.     best_x= current_x;
  424.     best_y= current_y;
  425.     for(i=0;i<numPoints;i++) if (!used[i]) {
  426.       candidate_x= X(i);
  427.       candidate_y= Y(i);
  428.       NI;
  429.       [self drawEdge:X(current_index):Y(current_index):X(i):Y(i)];
  430.       NXPing();
  431.       a=SIGNEDAREA(current_x,current_y,candidate_x,candidate_y, best_x,best_y);
  432.       if (a>=0.0)
  433.         { best_index=i; best_x=candidate_x; best_y=candidate_y;};
  434.       };
  435.     ch_vertex[nh++] = current_index = best_index;
  436.     };
  437.   /* 3. Jarvis' march - max to min */
  438.   used[min_index]=FALSE;
  439.   STATUS("Jarvis' march: max to min");
  440.   while (current_index!=min_index) {
  441.     used[current_index]=TRUE;
  442.     current_x= X(current_index);
  443.     current_y= Y(current_index);
  444.     best_x=current_x;
  445.     best_y=current_y;
  446.     for(i=0;i<numPoints;i++)  if (!used[i]) {
  447.       candidate_x= X(i);
  448.       candidate_y= Y(i);
  449.       NI;
  450.       [self drawEdge:X(current_index):Y(current_index):candidate_x:candidate_y];
  451.       NXPing();
  452.       a=SIGNEDAREA(current_x,current_y,candidate_x,candidate_y, best_x,best_y);
  453.       if (a>=0.0)
  454.         { best_index=i; best_x=candidate_x; best_y=candidate_y;};
  455.       };
  456.     ch_vertex[nh++] = current_index = best_index;
  457.     };
  458.   nch_vertices=nh;
  459.   NX_FREE(used);
  460.   chExists=YES;
  461.   STATUS("idle");
  462.   return self;
  463. }
  464.  
  465. /* Graham-hull utils */
  466.  
  467. #define QUAD(x,y) ((x<0) ? ((y<0)?3:2) : ((y<0)?4:1))
  468.  
  469. /* the following two declarations are nasty.  They are also the
  470.  * easient way to solve a problem using qsort().  In the Jarvis
  471.  * routine, the vertices are sorted by polar angle wrto the centroid
  472.  * as origin.  However, I want to sort an array of indices to the
  473.  * data rather than the data itself (for efficiency, etc.)  Since I need
  474.  * access to the data by name, and the data is an instance variable, I
  475.  * need to pass self to the plain-C routines and get the actual data by
  476.  * following self.
  477.  */
  478. static float mx,my; /* used in xcompar and doJarvisHull */
  479. static TwoDView *myself;
  480.  
  481. #define XX(i) myself->data[i*2]
  482. #define YY(i) myself->data[i*2+1]
  483.  
  484. int xcompar(int *pp1,int *pp2) /*used in Graham scan to sort pts by polar angle*/
  485. {
  486.   int p1,p2,q1,q2;
  487.   float x1,x2,y1,y2,a;
  488.   p1= *pp1; p2= *pp2;
  489.   x1= XX(p1)-mx; y1= YY(p1)-my;
  490.   x2= XX(p2)-mx; y2= YY(p2)-my;
  491.   /* cute li'l optimization.  If we can sort by quadrant, do it. */
  492.   q1=QUAD(x1,y1); q2=QUAD(x2,y2);
  493.   if (q1<q2)  return(-1);
  494.   if (q2<q1)  return(1);
  495.   /* if we get here, p1 & p2 are in same quadrant. shucks. */
  496.   a=SIGNEDAREA(0.0,0.0,x2,y2,x1,y1);
  497.   if (a<0.0) return(-1);
  498.   if (a==0.0) return(0);
  499.   return(1);
  500. }
  501.  
  502. /* be safe. */
  503. #undef XX
  504. #undef YY
  505.  
  506. - doGrahamHull
  507. {
  508.   /* Graham's scan for planar convex hull. From Preparata and Shamos */
  509.   int i,mindex=0,start,prev,current,next,nnext;
  510.   int t1,t2,t3;
  511.   float a,minx,xx;
  512.   int *pointers;
  513.   int nh;
  514.  
  515.   NXSetColor([highColorWell color]);
  516.  
  517.   /* THIS IS GROSS. */
  518.   myself = self;
  519.  
  520.   NX_MALLOC(pointers,int,numPoints);
  521.   nh=numPoints;
  522.   /* find point in hull interior (centroid will do)*/
  523.   STATUS("finding interior point");
  524.   mx=0.0; my=0.0; minx=999;
  525.   for(i=0;i<numPoints;i++) {
  526.     mx += X(i);
  527.     my += Y(i);
  528.     };
  529.   mx /= numPoints;
  530.   my /= numPoints;
  531.  
  532.   for(i=0;i<numPoints;i++) {
  533.     xx= X(i);
  534.     if (xx<minx) { minx=xx; mindex=i;};
  535.     };
  536.  
  537.   STATUS("sorting points by polar angle");
  538.   /* sort points by polar angle and distance */
  539.   for(i=0;i<numPoints;i++)  pointers[i]=i;
  540.   qsort((void *)pointers,(size_t)numPoints,sizeof(int),xcompar);
  541.  
  542.   /* find min-X vertex (guaranteed to be on CH) */
  543.   for(i=0;i<numPoints;i++) if (pointers[i]==mindex) break;
  544.  
  545.   start=current=i; /* point i is a hull vertex. */
  546.  
  547.   /* set up pointers in linked list */
  548.   prev=(current==0)?nh-1:current-1;
  549.   next=(current==nh-1)?0:current+1;
  550.   nnext=(next==nh-1)?0:next+1;
  551.   /* loop over all points */
  552.   STATUS("Performing Graham's scan");
  553.   while(pointers[next]!=pointers[start]) {
  554.     if (nh==3) break; /* triangle is ALWAYS a convex polygon */
  555.     t1= pointers[current]; t2= pointers[next]; t3= pointers[nnext];
  556.     NI;
  557.     [self drawEdge:X(current):Y(current):X(next):Y(next)];
  558.     [self drawEdge:X(current):Y(current):X(nnext):Y(nnext)];
  559.     [self drawEdge:X(nnext):Y(nnext):X(next):Y(next)];
  560.     NXPing();
  561.     a=SIGNEDAREA(X(t1),Y(t1),X(t2),Y(t2),X(t3),Y(t3));
  562.     if (a<0.0) { /* delete next vertex */
  563.       for(i=next+1;i<nh;i++) pointers[i-1]= pointers[i];
  564.       nh--;
  565.       if (start>=next) start--;
  566.       if (current>=next) current--;
  567.       if (current != start) {
  568.         /* back up one vertex in list */
  569.         current --;
  570.         if (current<0) current += nh;
  571.         prev=(current==0)?nh-1:current-1;
  572.         next=(current==nh-1)?0:current+1;
  573.         nnext=(next==nh-1)?0:next+1;
  574.         }
  575.       else { /* do not back up if current vertex is starting vertex. */
  576.         prev=(current==0)?nh-1:current-1;
  577.         next=(current==nh-1)?0:current+1;
  578.         nnext=(next==nh-1)?0:next+1;
  579.         };
  580.       }
  581.     else { /* move forward in linked list */
  582.       prev=current;
  583.       current=next;
  584.       next=(next==nh-1)?0:next+1;
  585.       nnext=(next==nh-1)?0:next+1;
  586.       };
  587.     };
  588.   for(i=0;i<nh;i++) ch_vertex[i]=pointers[i];
  589.   nch_vertices=nh;
  590.   NX_FREE(pointers);
  591.   chExists=YES;
  592.   return self;
  593. }
  594.  
  595. - doPrimMST
  596. {
  597.   /* Prim's quadratic MST algorithm - from Horowitz & Sahni */
  598.   int *near;
  599.   int i,j=0,k=0,l=0;
  600.   float mincost,x,xmin;
  601.  
  602. #ifdef DEBUG
  603.     MESSAGE("doPrimMST");
  604. #endif
  605.  
  606.   NX_MALLOC(near,int,numPoints);
  607.   bzero(near,numPoints*sizeof(int));
  608.   mincost=10000000000.0;
  609.   /* find shortest edge (O(n^2 time)) */
  610.   STATUS("finding shortest edge");
  611.   for(i=0;i<numPoints-1;i++) {
  612.    char buf[80];
  613.    sprintf(buf,"point %d",i);
  614.    STATUS(buf);
  615.    for(j=i+1;j<numPoints;j++) {
  616.      x=cost(XY(i),XY(j));
  617.      NI;
  618.      [self drawEdge:X(i):Y(i):X(j):Y(j)];
  619.      NXPing();
  620.      if (x<mincost) { mincost=x; k=i; l=j; };
  621.      };
  622.    };
  623.   /* (k,l) is the first edge in the MST */
  624.   mst_edge[0]=k; mst_edge[1]=l;
  625.   NI;
  626.   [self drawEdge:X(k):Y(k):X(l):Y(l)];
  627.   /* set up initial nearest-neighbor array for k and l */
  628.   for(i=0;i<numPoints;i++)
  629.     if (cost(XY(i),XY(l))<cost(XY(i),XY(k)))
  630.       near[i]=l;
  631.     else
  632.       near[i]=k;
  633.   near[k]= near[l]= -1;
  634.   /* add the remaining n-2 edges */
  635.   for(i=1;i<numPoints-1;i++) {
  636.     char buf[80];
  637.     sprintf(buf,"adding edge %d",i);
  638.     STATUS(buf);
  639.     xmin=100000000000.0;
  640.     for(k=0;k<numPoints;k++) 
  641.       if (near[k]!= -1) {
  642.         x=cost(XY(k),XY(near[k]));
  643.         if (x<xmin) { xmin=x; j=k;};
  644.         };
  645.     mst_edge[i*2] = j; mst_edge[i*2+1]= near[j];
  646.     NI;
  647.     [self drawEdge:X(j):Y(j):X(near[j]):Y(near[j])];
  648.     mincost += xmin;
  649.     near[j]= -1;
  650.     for(k=0;k<numPoints;k++)
  651.       if (near[k]!= -1)
  652.         if (cost(XY(k),XY(near[k])) > cost(XY(k),XY(j)))
  653.           near[k]=j;
  654.     };
  655.   NX_FREE(near);
  656.   NXPing();
  657.   mstExists=YES;
  658.   STATUS("idle");
  659.   return self;
  660. }
  661.  
  662. - erase
  663. {
  664.   NXRect r;
  665.   [[self getBounds:&r] lockFocus];
  666.   NXSetColor([bgColorWell color]);
  667.   NXRectFill(&r);
  668.   [self unlockFocus];
  669.   return self;
  670. }
  671.  
  672. - drawDT  /* focus must already be locked */
  673. {
  674.   int i;
  675.   for(i=0;i<ndt_edge;i++) {
  676.     int i1=dt_edge[i*2],i2=dt_edge[i*2+1];
  677.     char buf[80];
  678.     sprintf(buf,"DT edge %d",i);
  679.     STATUS(buf);
  680.     [self drawEdge:X(i1):Y(i1):X(i2):Y(i2)];
  681.     };
  682.   return self;
  683. }
  684.  
  685. - drawGG  /* focus must already be locked */
  686. {
  687.   int i;
  688.   for(i=0;i<ngg_edge;i++) {
  689.     int i1=gg_edge[i*2],i2=gg_edge[i*2+1];
  690.     char buf[80];
  691.     sprintf(buf,"GG edge %d",i);
  692.     STATUS(buf);
  693.     [self drawEdge:X(i1):Y(i1):X(i2):Y(i2)];
  694.     };
  695.   return self;
  696. }
  697.  
  698. - drawRNG  /* focus must already be locked */
  699. {
  700.   int i;
  701.   for(i=0;i<nrng_edge;i++) {
  702.     int i1=rng_edge[i*2],i2=rng_edge[i*2+1];
  703.     char buf[80];
  704.     sprintf(buf,"RNG edge %d",i);
  705.     STATUS(buf);
  706.     [self drawEdge:X(i1):Y(i1):X(i2):Y(i2)];
  707.     };
  708.   return self;
  709. }
  710.  
  711. /*--------------------------------------------------------------------
  712. This function does the actual drawing for the algorithms. Most of the 
  713. routines merely draw line between sets of vertices.
  714.  
  715.                                                  Bill Roth/1991-Nov-17
  716. --------------------------------------------------------------------*/
  717. - drawSelf:(NXRect *)rects:(int)rectCount
  718. {
  719.   int i;
  720. #ifdef DEBUG
  721.   fprintf(stderr,"drawSelf (%d points)\n",numPoints);
  722. #endif
  723.  
  724.   STATUS("redisplaying");
  725.   NXSetColor([bgColorWell color]);
  726.   NXRectFill(&rects[0]); //fill page with current color.
  727.  
  728.   setUpScaling(self); // set up scaling
  729.  
  730.   setUpDrawing(self);
  731.   NXSetColor([self->fgColorWell color]);
  732.   for(i=0;i<numPoints;i++) {
  733. #ifdef PRINTLOTS
  734.     fprintf(stderr,"%d: %f %f\n",i,X(i),Y(i));
  735. #endif
  736.     PSnewpath();
  737.     PSarc(X(i),Y(i),width,0.0,360.0);
  738.     PSfill();
  739.     };
  740.   NXPing();
  741.  
  742.   /* draw selected data structure */
  743.   if (displayFlag != DISP_PTS_ONLY) {
  744. #ifdef DEBUG
  745.     MESSAGE("drawing everything");
  746. #endif
  747.  
  748.     switch(operation) {
  749.       case OP_PRIM_MST:
  750.       case OP_KRUSKAL_MST: if (mstExists) {
  751.         for(i=0;i<numPoints-1;i++) {
  752.           int i1=mst_edge[i*2], i2=mst_edge[i*2+1];
  753.           PSnewpath();
  754.           PSmoveto(X(i1),Y(i1));
  755.           PSlineto(X(i2),Y(i2));
  756.           PSstroke();
  757.           NXPing();
  758.           };
  759.         break;
  760.         };
  761.       case OP_JARVIS_HULL:
  762.       case OP_GRAHAM_HULL: if (chExists) {
  763.         PSnewpath();
  764.         PSmoveto(X(ch_vertex[0]),Y(ch_vertex[0]));
  765.         for(i=1;i<nch_vertices;i++) PSlineto(X(ch_vertex[i]),Y(ch_vertex[i]));
  766.         PSclosepath();
  767.         PSstroke();
  768.         NXPing();
  769.         break;
  770.         };
  771.       case OP_VORONOI_DIAGRAM: if (vorExists) {
  772.         float *foo;
  773.         NX_MALLOC(foo,float,numPoints*2);
  774.         for(i=0;i<numPoints;i++) {
  775.           int nv,j;
  776.           if ((nv=find_vregion(i,foo)) == -1) {
  777.             NXRunAlertPanel("Voronoi","find_vregion (%d) failed",
  778.                             NULL,NULL,NULL,i);
  779.             break;
  780.             };
  781.           PSnewpath();
  782.           PSmoveto(foo[0],foo[1]);
  783.           for(j=1;j<nv;j++) PSlineto(foo[j*2],foo[j*2+1]);
  784.           PSclosepath();
  785.           PSstroke();
  786.           NXPing();
  787.           };
  788.         NX_FREE(foo);
  789.         break;
  790.         };
  791.       case OP_DELAUNAY_TRIANGULATION: if (vorExists) [self drawDT]; break;
  792.       case OP_GABRIEL_GRAPH: if (ggExists)  [self drawGG]; break;
  793.       case OP_RELATIVE_NEIGHBORHOOD_GRAPH: if (rngExists) [self drawRNG]; break;
  794.       case OP_TSP_STUPID_NEIGHBOR:
  795.       case OP_TSP_NEAREST_NEIGHBOR: 
  796.       case OP_TSP_CHEAPEST_INSERTION:
  797.       case OP_TSP_NEAREST_ADDITION:
  798.       case OP_TSP_FARTHEST_INSERTION:
  799.      [self drawTSP]; break;
  800.       };
  801.     };
  802.   NXPing();
  803.   STATUS("idle");
  804.   return self;
  805. }
  806.  
  807. /* draw an edge with given endpoints */
  808. -drawEdge:(float)x1:(float)y1:(float)x2:(float)y2
  809. { /*assumes focus is locked already and attributes (color, width) already set*/
  810.   PSnewpath();
  811.   PSmoveto(x1,y1);
  812.   PSlineto(x2,y2);
  813.   PSstroke();
  814.   return self;
  815. }
  816.  
  817. - doVoronoiDiagram
  818. {
  819.   NXRect r;
  820.   float xmin,xmax,ymin,ymax;
  821.  
  822.   STATUS("Finding Voronoi diagram");
  823.   [self getBounds:&r];
  824.   xmin=-1.01;
  825.   xmax=1.01;
  826.   ymin=-1.01;
  827.   ymax=1.01;
  828.  
  829.   if (load_vsites(numPoints,data,xmin,ymin,xmax,ymax)) {
  830.     NXRunAlertPanel("Voronoi","load_vsites failed",NULL,NULL,NULL);
  831.     vorExists=NO;
  832.     return self;
  833.     };
  834.   vorExists=YES;
  835.   return self;
  836. }
  837.  
  838. static void checkedge(TwoDView *s,int i1,int i2)
  839. {
  840.   int i;
  841.   int ii1=MIN(i1,i2),ii2=MAX(i1,i2);
  842.   for(i=0;i<s->ndt_edge;i++)
  843.     if ((s->dt_edge[i*2]==ii1)&&(s->dt_edge[i*2+1]==ii2)) return;
  844.   s->dt_edge[s->ndt_edge*2]=ii1;
  845.   s->dt_edge[s->ndt_edge*2+1]=ii2;
  846.   s->ndt_edge++;
  847. }
  848.  
  849. - doDelaunayTriangulation
  850. {
  851.   int ndt,i;
  852.   TRI *tris;
  853.   [self doVoronoiDiagram]; /* cheat! */
  854.  
  855.   STATUS("building Delaunay triangulation data structures");
  856.   /* from list of triangles, obtain minimal list of edged (toss dups) */
  857.   if ((ndt=find_dtriangles(&tris)) == -1) {
  858.     NXRunAlertPanel("Delaunay","find_dtriangles() failed",
  859.                     NULL,NULL,NULL);
  860.     return self;
  861.     };
  862.  
  863.   NX_MALLOC(dt_edge,int,2*3*ndt);
  864.   ndt_edge=0;
  865.  
  866.   for(i=0;i<ndt;i++) {
  867.     int p1=tris[i].v1->pid;
  868.     int p2=tris[i].v2->pid;
  869.     int p3=tris[i].v3->pid;
  870.     checkedge(self,p1,p2);
  871.     checkedge(self,p1,p3);
  872.     checkedge(self,p2,p3);
  873.     };
  874.   dtExists=YES;
  875. #ifdef PRINTLOTS
  876.   for(i=0;i<ndt_edge;i++)
  877.     fprintf(stderr,"%d: %d %d\n",i,dt_edge[i*2],dt_edge[i*2+1]);
  878. #endif
  879.   return self;
  880. }
  881.  
  882.  
  883. - doGabrielGraph
  884. {
  885.   int i;
  886.  
  887.   if (!dtExists)[self doDelaunayTriangulation];
  888.   /* we need to see the Delaunay graph while we're animating */
  889.   NXSetColor([fgColorWell color]);
  890.   PSsetinstance(NO);
  891.   [[[self drawDT] window] flushWindow];
  892.   NXSetColor([highColorWell color]);
  893.   PSsetinstance(YES);
  894.  
  895.   ngg_edge=ndt_edge;
  896.   NX_MALLOC(gg_edge,int,2*ngg_edge);
  897.  
  898.   bcopy(dt_edge,gg_edge,2*ngg_edge*sizeof(int));
  899.  
  900.  
  901.   /*for each DT edge, check circle of influence */
  902.   for(i=0;i<ngg_edge;i++) {
  903.     int i1=gg_edge[i*2], i2=gg_edge[i*2+1];
  904.     int j;
  905.     float x1=X(i1),y1=Y(i1);
  906.     float x2=X(i2),y2=Y(i2);
  907.     float cx=(x1+x2)/2,cy=(y1+y2)/2,r=0.5*sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
  908.     char buf[80];
  909. #ifdef PRINTLOTS
  910.     fprintf(stderr,"edge %d (%d %d)\n",i,i1,i2);
  911.     fprintf(stderr,"(%f %f) -> (%f %f)\n",x1,y2,x2,y2);
  912.     fprintf(stderr,"center %f %f rad %f\n",cx,cy,r);
  913. #endif
  914.     sprintf(buf,"considering Delaunay edge %d",i);
  915.     STATUS(buf);
  916.     NI;
  917.     PSnewpath();
  918.     PSarc(cx,cy,r,0.0,360.0);
  919.     PSstroke();
  920.     NXPing();
  921.  
  922.     /* check to see if any other vertex in within the circle of influence.
  923.        if so, remove the edge from the GG */
  924.     for(j=0;j<numPoints;j++) {
  925.       if ((j!=i1)&&(j!=i2)) {
  926.         float x=X(j),y=Y(j),d=(x-cx)*(x-cx)+(y-cy)*(y-cy)-r*r;
  927. #ifdef PRINTLOTS
  928.         fprintf(stderr,"checking %d (%f %f) d %g\n",j,x,y,d);
  929. #endif
  930.         if (d<0.0) { /* clobber edge */
  931.           int k;
  932. #ifdef DEBUG
  933.           fprintf(stderr,"die.\n");
  934. #endif
  935.           for(k=i;k<ngg_edge-1;k++) {
  936.             gg_edge[k*2]=gg_edge[(k+1)*2];
  937.             gg_edge[k*2+1]=gg_edge[(k+1)*2+1];
  938.             };
  939.           ngg_edge--;
  940.           /* highlight the dead edge */
  941.           PSsetinstance(NO);
  942.           PSnewpath();
  943.           PSmoveto(x1,y1);
  944.           PSlineto(x2,y2);
  945.           PSstroke();
  946.           PSsetinstance(YES);
  947.           i--;
  948.           break;
  949.           };
  950.         };
  951.       };
  952.     };
  953.   NX_REALLOC(gg_edge,int,2*ngg_edge);
  954.   ggExists=YES;
  955.   return self;
  956. }
  957.  
  958. - doRelativeNeighborhoodGraph
  959. {
  960.   int i;
  961.  
  962.   if (!dtExists)[self doDelaunayTriangulation];
  963.   /* we need to see the Delaunay graph while we're animating */
  964.   PSsetinstance(NO);
  965.   NXSetColor([fgColorWell color]);
  966.   [[[self drawDT] window] flushWindow];
  967.   NXSetColor([highColorWell color]);
  968.   PSsetinstance(YES);
  969.  
  970.   nrng_edge=ndt_edge;
  971.   NX_MALLOC(rng_edge,int,2*nrng_edge);
  972.  
  973.   bcopy(dt_edge,rng_edge,2*nrng_edge*sizeof(int));
  974.  
  975.  
  976.   /*for each DT edge, check lune of influence */
  977.   for(i=0;i<nrng_edge;i++) {
  978.     int i1=rng_edge[i*2], i2=rng_edge[i*2+1];
  979.     int j;
  980.     float x1=X(i1),y1=Y(i1);
  981.     float x2=X(i2),y2=Y(i2);
  982.     float r2=(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
  983.     float r=sqrt(r2);
  984.     char buf[80];
  985. #ifdef PRINTLOTS
  986.     fprintf(stderr,"edge %d (%d %d)\n",i,i1,i2);
  987.     fprintf(stderr,"(%f %f) -> (%f %f)\n",x1,y2,x2,y2);
  988.     fprintf(stderr,"rad^2 %f\n",r2);
  989. #endif
  990.     sprintf(buf,"considering Delaunay edge %d",i);
  991.     STATUS(buf);
  992.     NI;
  993.     PSnewpath();
  994.     PSarc(x1,y1,r,0.0,360.0);
  995.     PSmoveto(x2,y2); /* eliminate annoying connector */
  996.     PSarc(x2,y2,r,0.0,360.0);
  997.     PSstroke();
  998.     NXPing();
  999.  
  1000.     /* check to see if any other vertex in within the lune of influence.
  1001.        if so, remove the edge from the RNG */
  1002.     for(j=0;j<numPoints;j++) {
  1003.       if ((j!=i1)&&(j!=i2)) {
  1004.         float x=X(j),y=Y(j);
  1005.         float d1=(x-x1)*(x-x1)+(y-y1)*(y-y1)-r*r;
  1006.         float d2=(x-x2)*(x-x2)+(y-y2)*(y-y2)-r*r;
  1007. #ifdef PRINTLOTS
  1008.         fprintf(stderr,"checking %d (%f %f) d1 %g d2 %g\n",j,x,y,d1,d2);
  1009. #endif
  1010.         if ((d1<0.0)&&(d2<0.0)) { /* clobber edge */
  1011.           int k;
  1012. #ifdef PRINTLOTS
  1013.           fprintf(stderr,"die.\n");
  1014. #endif
  1015.           for(k=i;k<nrng_edge-1;k++) {
  1016.             rng_edge[k*2]=rng_edge[(k+1)*2];
  1017.             rng_edge[k*2+1]=rng_edge[(k+1)*2+1];
  1018.             };
  1019.           nrng_edge--;
  1020.           /* highlight the dead edge */
  1021.           PSsetinstance(NO);
  1022.           PSnewpath();
  1023.           PSmoveto(x1,y1);
  1024.           PSlineto(x2,y2);
  1025.           PSstroke();
  1026.           PSsetinstance(YES);
  1027.           i--;
  1028.           break;
  1029.           };
  1030.         };
  1031.       };
  1032.     };
  1033.   NX_REALLOC(rng_edge,int,2*nrng_edge);
  1034.   rngExists=YES;
  1035.   return self;
  1036. }
  1037.  
  1038. - saveTIFF:sender
  1039. {
  1040.   NXRect r;
  1041.   id bm=[NXBitmapImageRep alloc];
  1042.   id sp=[SavePanel new];
  1043.   NXStream *stream;
  1044.   int fd;
  1045.  
  1046.   [self lockFocus];
  1047.   [self getBounds:&r];
  1048.   [bm initData:NULL fromRect:&r];
  1049.   [self unlockFocus];
  1050.   
  1051.   [sp setRequiredFileType:"tiff"];
  1052.   if (![sp runModal]) return self;
  1053.   fd=open([sp filename],O_CREAT|O_RDWR,0644);
  1054.   /*[sp free]; screaming death! screaming death! don't do this! */
  1055.   stream = NXOpenFile(fd,NX_READWRITE);
  1056.   if (!stream) {
  1057.     NXRunAlertPanel("TIFF","NXOpenFile failed",NULL,NULL,NULL);
  1058.     perror("NXOpenFile");
  1059.     [bm free];
  1060.     close(fd);
  1061.     return self;
  1062.     };
  1063.   STATUS("saving image as TIFF");
  1064.   [bm writeTIFF:stream usingCompression:NX_TIFF_COMPRESSION_LZW];
  1065.   [bm free];
  1066.   NXClose(stream);
  1067.   close(fd);
  1068.   STATUS("idle");
  1069.   return self;
  1070. }
  1071.  
  1072. - saveData:sender
  1073. {
  1074.   id sp=[SavePanel new];
  1075.   FILE *fp;
  1076.   int i;
  1077.  
  1078.   [sp setRequiredFileType:"2Ddata"];
  1079.   if (![sp runModal]) return self;
  1080.   fp=fopen([sp filename],"w");
  1081.   /*[sp free];*/
  1082.   if (!fp) {
  1083.     NXRunAlertPanel("Data","Couldn't open file",NULL,NULL,NULL);
  1084.     return self;
  1085.     };
  1086.   STATUS("saving data");
  1087.   fprintf(fp,"%d\n",numPoints);
  1088.   for(i=0;i<numPoints;i++) fprintf(fp,"%g %g\n",X(i),Y(i));
  1089.   fclose(fp);
  1090.   STATUS("idle");
  1091.   return self;
  1092. }
  1093.  
  1094. - openData:sender
  1095. {
  1096.   id op=[OpenPanel new];
  1097.   const char *const types[] = { "2Ddata" , NULL};
  1098.   FILE *fp;
  1099.   int i;
  1100.  
  1101.   [op setRequiredFileType:"2Ddata"];
  1102.   [op runModalForDirectory:"." file:"" types:types];
  1103.   chdir([op directory]);
  1104.   if (![op filenames]) {
  1105.     /*[op free];*/
  1106.     return self;
  1107.     };
  1108.  
  1109.   fp=fopen(*[op filenames],"r");
  1110.   /*[op free];*/
  1111.   if (!fp) {
  1112.     NXRunAlertPanel("Open","Couldn't open file",NULL,NULL,NULL);
  1113.     return self;
  1114.     };
  1115.  
  1116.   STATUS("reading data");
  1117.   fscanf(fp,"%d ",&numPoints);
  1118.   initGeometryStuff(self,numPoints);
  1119.   for(i=0;i<numPoints;i++) {
  1120.     fscanf(fp,"%f %f ",&X(i),&Y(i));
  1121.     };
  1122.   fclose(fp);
  1123.   [self disp:0];
  1124.   STATUS("idle");
  1125.   return self;
  1126. }
  1127.  
  1128. - close:sender
  1129. {
  1130.   initGeometryStuff(self,0);
  1131.   [self erase];
  1132.   return self;
  1133. }
  1134.   
  1135. -lineWidthChanged:sender
  1136. {
  1137.   char buf[80];
  1138.   sprintf(buf,"Line width: %6.3f\n",[sender floatValue]);
  1139.   [lineWidthText setStringValue:buf];
  1140.   /* could redraw self here */
  1141.   if (autoUpdate) [self display];
  1142.   return self;
  1143. }
  1144.  
  1145.  
  1146. -setFgColorWell:sender
  1147. {
  1148.   fgColorWell = sender;
  1149.   [sender setColor:NXConvertGrayToColor(0.0)];
  1150.   return self;
  1151. }
  1152.  
  1153. -setBgColorWell:sender
  1154. {
  1155.   bgColorWell = sender;
  1156.   [sender setColor:NXConvertGrayToColor(1.0)];
  1157.   return self;
  1158. }
  1159.  
  1160.  
  1161. -setHighColorWell:sender
  1162. {
  1163.   highColorWell = sender;
  1164.   [sender setColor:NXConvertGrayToColor(0.666)];
  1165.   return self;
  1166. }
  1167.  
  1168. - (BOOL)acceptsFirstResponder { return YES; }
  1169. - (BOOL)acceptsFirstMouse { return YES; }
  1170.  
  1171. - copy:sender
  1172. {
  1173.   id pb=[Pasteboard new];
  1174.   NXStream *stream;
  1175.   char *d;
  1176.   int length,maxLength;
  1177.  
  1178.   STATUS("copying image to clipboard");
  1179.   [pb declareTypes:&NXPostScriptPboard num:1 owner:self];
  1180.   stream=NXOpenMemory(NULL,0,NX_WRITEONLY);
  1181.   [self copyPSCodeInside:NULL to:stream];
  1182.   NXGetMemoryBuffer(stream,&d,&length,&maxLength);
  1183.   [pb writeType:NXPostScriptPboard data:d length:length];
  1184.   NXCloseMemory(stream,NX_FREEBUFFER);
  1185.   [pb free];
  1186.   STATUS("idle");
  1187.   return self;
  1188. }
  1189.  
  1190. - saveEPS:sender
  1191. {
  1192.   NXStream *stream;
  1193.   id sp = [SavePanel new];
  1194.   int fd;
  1195.  
  1196.   [sp setRequiredFileType:"eps"];
  1197.   if (![sp runModal]) return self;
  1198.   fprintf(stderr,"filename %s\n",[sp filename]);
  1199.   fd=open([sp filename],O_CREAT|O_WRONLY,0644);
  1200.   /*[sp free];*/
  1201.   stream = NXOpenFile(fd,NX_WRITEONLY);
  1202.   if (!stream) {
  1203.     NXRunAlertPanel("EPS","NXOpenFile failed",NULL,NULL,NULL);
  1204.     perror("NXOpenFile");
  1205.     return self;
  1206.     };
  1207.   STATUS("saving image as EPS");
  1208.   [self copyPSCodeInside:NULL to:stream];
  1209.   NXClose(stream);
  1210.   close(fd);
  1211.   STATUS("idle");
  1212.   return self;
  1213. }
  1214. /*--------------------------------------------------------------------
  1215. Message from App object.
  1216.  
  1217.                                                  Bill Roth/1991-Dec-01
  1218. --------------------------------------------------------------------*/
  1219.  
  1220. - appDidInit: sender;
  1221. {
  1222.     drawTime = [timeSlider intValue];
  1223.     [timeField setIntValue: drawTime at: 0];
  1224.     [timeField setEnabled: NO]; // agree with NIB
  1225.     [timeSlider setEnabled: NO];// agree with NIB
  1226.     return self ;
  1227. }
  1228. - doOptimize :sender
  1229. {
  1230.     [self Optimize];
  1231.     return self;
  1232. }
  1233. @end
  1234.